package com.autumn.config.client;

import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.List;

import org.springframework.beans.BeansException;
import org.springframework.beans.factory.BeanCreationException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.env.Environment;

import com.alibaba.dubbo.config.annotation.Reference;
import com.alibaba.dubbo.config.spring.ReferenceBean;
import com.autumn.config.api.CloudConfigService;
import com.autumn.dubbo.spring.boot.DubboCommonAutoConfiguration;
import com.autumn.dubbo.spring.boot.DubboConsumerAutoConfiguration;
import com.autumn.dubbo.spring.boot.DubboProperties;
import com.autumn.dubbo.spring.boot.bean.ClassIdBean;
import com.autumn.spring.boot.properties.AutumnCloudProperties;

import javassist.Modifier;

/**
 * 
 * @author 老码农
 *
 *         2018-07-02 10:44:37
 */
@Configuration
@EnableConfigurationProperties({ DubboProperties.class, AutumnCloudProperties.class })
public class ConfigServiceBootstrapConfiguration extends DubboCommonAutoConfiguration {

	/**
	 * 10秒
	 */
	@Reference(timeout = 10000)
	private CloudConfigService autumnCloudConfigService;

	@Autowired
	private ApplicationContext applicationContext;

	/**
	 * 
	 * @param properties
	 * @return
	 *
	 */
	@Bean
	@ConditionalOnMissingBean(ConfigServicePropertySourceLocator.class)
	@ConditionalOnProperty(value = "autumn.cloud.config.enabled", matchIfMissing = true)
	public ConfigServicePropertySourceLocator configServicePropertySourceLocator(DubboProperties dubboProperties,
			AutumnCloudProperties autumnCloudProperties) {
		this.initConsumerBean(dubboProperties);
		return new ConfigServicePropertySourceLocator(this.autumnCloudConfigService, autumnCloudProperties);
	}

	/**
	 * 初始化消息者的 Bean
	 * 
	 * @param dubboProperties
	 *            属性
	 *
	 */
	private void initConsumerBean(DubboProperties dubboProperties) {
		List<Field> lst = getObjectBeanFields(this.getClass(), null);
		try {
			this.initIdConfigMap(dubboProperties);
			for (Field field : lst) {
				Reference reference = field.getAnnotation(Reference.class);
				if (reference != null) {
					ReferenceBean<?> referenceBean = this.getConsumerBean(field, reference, dubboProperties);
					Class<?> interfaceClass = referenceBean.getInterfaceClass();
					String group = referenceBean.getGroup();
					String version = referenceBean.getVersion();
					ClassIdBean classIdBean = new ClassIdBean(interfaceClass, group, version);
					Object dubboReference = DubboConsumerAutoConfiguration.getDubboReference(classIdBean);
					if (dubboReference == null) {
						synchronized (DubboConsumerAutoConfiguration.DUBBO_REFERENCES_LOCK) {
							dubboReference = DubboConsumerAutoConfiguration.getDubboReference(classIdBean);
							if (dubboReference == null) {
								referenceBean.afterPropertiesSet();
								dubboReference = referenceBean.getObject();
								DubboConsumerAutoConfiguration.setDubboReference(classIdBean, dubboReference);
							}
						}
					}
					field.setAccessible(true);
					field.set(this, dubboReference);
				}
			}
		} catch (Exception e) {
			throw new BeanCreationException("无法初始化 " + CloudConfigService.class.getName() + " 类型。", e);
		}
	}

	private List<Field> getObjectBeanFields(Class<?> type, List<Field> fields) {
		if (fields == null) {
			fields = new ArrayList<>();
		}
		for (Field field : type.getDeclaredFields()) {
			int mod = field.getModifiers();
			if (!Modifier.isStatic(mod) && !Modifier.isFinal(mod)) {
				fields.add(field);
			}
		}
		if (type.getSuperclass() != null && !type.getSuperclass().equals(Object.class)) {
			getObjectBeanFields(type.getSuperclass(), fields);
		}
		return fields;
	}

	/**
	 * 初始化消费者 Bean
	 * 
	 * @param beanName
	 * @param field
	 * @param reference
	 * @param dubboProperties
	 * @return
	 * @throws BeansException
	 *
	 */
	private <T> ReferenceBean<T> getConsumerBean(Field field, Reference reference, DubboProperties dubboProperties)
			throws BeansException {
		ReferenceBean<T> referenceBean = new ReferenceBean<T>(reference);
		if ((reference.interfaceClass() == null || reference.interfaceClass() == void.class)
				&& (reference.interfaceName() == null || "".equals(reference.interfaceName()))) {
			referenceBean.setInterface(field.getType());
		}
		String typeName = field.getDeclaringClass().getName();
		Environment environment = this.applicationContext.getEnvironment();
		String application = reference.application();
		referenceBean.setApplication(this.parseApplication(application, dubboProperties, environment, field.getName(),
				"application", application));
		String module = reference.module();
		referenceBean.setModule(
				this.parseModule(module, dubboProperties, environment, typeName, field.getName(), "module", module));
		String[] registries = reference.registry();
		referenceBean.setRegistries(
				this.parseRegistries(registries, dubboProperties, environment, typeName, field.getName(), "registry"));
		String monitor = reference.monitor();
		referenceBean.setMonitor(this.parseMonitor(monitor, dubboProperties, environment, typeName, field.getName(),
				"monitor", monitor));
		String consumer = reference.consumer();
		referenceBean.setConsumer(this.parseConsumer(consumer, dubboProperties, environment, typeName, field.getName(),
				"consumer", consumer));

		referenceBean.setApplicationContext(this.applicationContext);
		return referenceBean;
	}

	@Override
	protected String buildErrorMsg(String... errors) {
		if (errors == null || errors.length != 4) {
			return super.buildErrorMsg(errors);
		}
		return new StringBuilder().append("type=").append(errors[0]).append(", field=").append(errors[1]).append(", ")
				.append(errors[2]).append("=").append(errors[3]).append(" not found in multi configs").toString();
	}

}
